#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include "ftp_def.h"
//#pragma  comment(lib,"WS2_32.lib")
#define TRUE 1
#define FALSE 0

// define trans_mode related value
int trans_mode;
#define TRANSMODE_ASCII 0
#define TRANSMODE_BINARY 1 

// function declaration
int get_trans_mode();
int set_trans_mode(int mode);
int reset_init_pwd(const char * mem_init_pwd);
int creat_socket(int port_num);
int listen_and_connect(int sockfd);
int command_get(int data_sock, int con_sock, char command[]);
int command_put(int data_sock, int con_sock, char command[]);
int command_delete(int con_sock, char command[]);
int command_cd(int data_sock, int con_sock, char command[]);
int command_mkdir(int data_sock, int con_sock, char command[]);
int command_pwd(int data_sock, int con_sock);
int command_ls(int data_sock, int con_sock);

char* cd(char* buff, int length);
void send_msg(const char*msg ,int con_sock);
int command_set(int con_sock, char* command);
int command_quit(int con_sock, int data_sock);
int process_msg(int con_sock, int data_sock);

int get_trans_mode(){
    return trans_mode;
}

int set_trans_mode(int mode){
    if( mode == TRANSMODE_ASCII || mode == TRANSMODE_BINARY){
        trans_mode = mode;
        return 0;
    } 
    return -1;
}

int reset_init_pwd(const char * mem_init_pwd){
    chdir(mem_init_pwd);
    return 0;
}

int creat_socket(int port_num){
    int new_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (new_sock < 0){
        printf("Fail to create socket on %d\n", port_num);
        return -1;
    }
    
    // set the address of the new_sock
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port_num);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // bind the new_sock with its local address
    int bytes = bind(new_sock, (struct sockaddr *)&addr, sizeof(addr));
    if (bytes < 0){
        printf("Fail to bind socket\n");
        return -1;
    }
    printf("create socket in port %d\n", port_num);

    return new_sock;
}

int listen_and_connect(int sockfd){
    // listen to client's connection 
    // MAX queue length = 10
    if (0 != listen(sockfd, 10)) {
        printf("listen failed"); 
        return -1;
    }

    // accept connection
    struct sockaddr_in client_address;
    int len = sizeof(client_address);
    int bytes = accept(sockfd, (struct sockaddr *)&client_address,(socklen_t *) &len);
    if (bytes < 0){
        printf("accept failed"); 
        return -1;
    }
    printf("Succeed link to %s\n", inet_ntoa(client_address.sin_addr));

    return bytes;
}

int command_get(int data_sock, int con_sock, char command[]){
    printf("receive get command\n");

    char path[BYTE_LENGTH] = { 0 };
    int send_times = 0, bytes = 0;
    char file_data[BYTE_LENGTH] = { 0 };
    strncpy(path, command + 4, BYTE_LENGTH);

    FILE* this_file = NULL;
    if((this_file = fopen(path, get_read_mode(get_trans_mode()))) == NULL){
        printf("File %s not exists.\n", path);
        // file not exist: send send_times/recv_times = 0 to client
        send_times = 0;
        if(send(con_sock, &send_times, sizeof(int), 0)<0){
            return command_quit(con_sock,data_sock);
        } 
        printf("Get command aborted. No such file\n");
        return TRUE;
    } 

    while (fread(file_data,sizeof(char),BYTE_LENGTH,this_file) > 0){
        send_times++;
    }
    rewind(this_file);
    printf("send send_times = %d\n",send_times);

    // file exists: send send_times/recv_times to client
    // if fail to send times
    if(send(con_sock, &send_times, sizeof(int), 0)<0){
        return command_quit(con_sock,data_sock);
    }
    // send file from data port
    while ((bytes = fread(file_data,sizeof(char),BYTE_LENGTH,this_file))>0){
        // if fail to send file data: quit the command
        if(send(data_sock, file_data, bytes, 0)<0){
            printf("send error\n");
            fclose(this_file);
            return command_quit(con_sock,data_sock);
        }else{
            printf("send ,size =%d\n",bytes);
        }
        memset(file_data, 0, BYTE_LENGTH);
    }

    // send reply code
    send_msg("200 Command okay.",con_sock);
    fclose(this_file);

    return TRUE;
}

int command_put(int data_sock, int con_sock, char command[]){       
    printf("receive put command\n");
    
    char filename[BYTE_LENGTH] = { 0 };
    char path[BYTE_LENGTH] = { 0 };
    char * cpy_str = NULL;
    cpy_str = strncpy(path, command + 4, BYTE_LENGTH);
    // delete blank space
    remove_space_both_ends(path);
    // delete path of dirs
    parse_filename_from_path(path, filename);
    // get the unique filename
    get_aliant_filename(filename);
    printf("Path:%s, File:%s\n",path, filename);

    // check the existence of the filename
    FILE* this_file = NULL;
    if ( (this_file = fopen(filename, get_read_mode(get_trans_mode()) )) != NULL){
        printf("Error: File <%s> already exists.\n",filename);
        // send reply code
        send_msg("502 File already exists.",con_sock);
        fclose(this_file);
        return TRUE;
    }

    if ( (this_file = fopen(filename, get_write_mode( get_trans_mode() ) ) )== NULL){
        printf("Error: File path <%s> can not be created.\n",filename);
        // send reply code
        send_msg("502 File path can not be created.",con_sock);
        return TRUE;
    }

    int recv_size = 0;
    int recv_times = 0;
    // send the success of creation to client
    send(con_sock, filename, BYTE_LENGTH,0);
     
    // get recv_times from client
    if(recv(con_sock, &recv_times, sizeof(int), 0) < 0){
        printf("recv file recv_times error\n");
        return command_quit(con_sock,data_sock);
    }else{
        printf("should recv %d times\n",recv_times);
    }

    char file_data[BYTE_LENGTH] = { 0 };
    printf("should recv %d times\n",recv_times);
    // receive file from data port
    for(int i = 0; i < recv_times; i++){
        // if fail to receive file data: quit the command
        if((recv_size = recv(data_sock, file_data, BYTE_LENGTH, 0)) < 0){
            return command_quit(con_sock,data_sock);
        }
        printf("recv data,size =%d\n",recv_size);
        fwrite(file_data,sizeof(char),recv_size,this_file);
        printf("i=%d;\n",i);
        bzero(&file_data, sizeof(file_data));
        printf("i=%d;\n",i);
    }

    // send reply code
    send_msg("200 Put file okay.",con_sock);
    fclose(this_file);

    return TRUE;
}

int command_delete(int con_sock, char command[]){
    printf("receive delete\n");

    char path[BYTE_LENGTH] = {0};
    strncpy(path, command + 7, BYTE_LENGTH);
    // delete blank space 
    remove_space_both_ends(path);
    printf("receive delete file is <%s>\n", path);

    // delete file
    int remove_success = remove(path);

    // send reply code
    if(remove_success < 0)
        send_msg("502 Command not implemented. No delete file.",con_sock);
    else
        send_msg("200 Command okay.",con_sock);

    return TRUE;
}

int command_cd(int data_sock, int con_sock, char command[]){
    printf("receive cd\n");

    char pathname[BYTE_LENGTH] = {0};
    strncpy(pathname, command + 3, BYTE_LENGTH);
    // delete blank space
    remove_space_both_ends(pathname);

    printf("before cd path is:%s\n",pathname);
    // check the existence of pathname and run "cd"
    cd(pathname, BYTE_LENGTH);
    printf("after cd path is:%s\n",pathname);

    // if fail to send data: quit the command
    if(send(data_sock, pathname, BYTE_LENGTH, 0) < 0){
        return command_quit(con_sock,data_sock);
    }

    // send reply code
    if (strcmp(pathname,"No corresponding directory found") == 0){
        send_msg("502 Command not implemented. No corresponding directory found.",con_sock);
    }else{
        send_msg("200 Command okay.",con_sock);
    }

    return TRUE;
}

int command_mkdir(int data_sock, int con_sock, char command[]){
    printf("receive mkdir\n");
    char buff[BYTE_LENGTH]= { 0 };

    strncpy(buff, command + 6, BYTE_LENGTH);
    remove_space_both_ends(buff);

    // create the new directory
    int mkdir_success = mkdir(buff,0777);

    if(mkdir_success < 0){
        send_msg("502 Command not implemented. Can't create such directory.",con_sock);
    }
    else{
        send_msg("200 Command okay.",con_sock);
    }

    return TRUE;
}

int command_pwd(int data_sock, int con_sock){
    printf("receive pwd\n");
    char buff[BYTE_LENGTH] = { 0 };

    // get the current directory
    getcwd(buff, BYTE_LENGTH);

    if(send(data_sock, buff, BYTE_LENGTH,0) < 0){
        perror("send fail\n");
        return command_quit(con_sock,data_sock);
    }
    else {
        send_msg("200 Command okay.",con_sock);
    }

    return TRUE;
}

int command_ls(int data_sock, int con_sock){
    printf("receive ls\n");
    char buff[BYTE_LENGTH] = { 0 };

    getcwd(buff,BYTE_LENGTH);
    DIR *dir = opendir(buff);
    if (dir == NULL){
        printf("path not exist!\n");
        return command_quit(con_sock, data_sock);
    }
    unsigned int now_length = 0;
    
    // get the ls data into buff
    struct dirent *entry;
    strcat(buff,"\n");
    while ((entry = readdir(dir)) != NULL){
        now_length+=(strlen(entry->d_name)+1);
        if(now_length>=BYTE_LENGTH){
            printf("The buffer length is too short, and the array is out of bounds");
            return command_quit(con_sock, data_sock);
        }
        strcat(buff,entry->d_name);
        strcat(buff,"\n");
    }
    closedir(dir);

    if(send(data_sock, buff, BYTE_LENGTH,0) < 0){
        perror("send fail\n");
        return command_quit(con_sock, data_sock);
    }
    else {
        send_msg("200 Command okay.", con_sock);
    }

    return TRUE;
}

char* cd(char* buff, int length){
    // check the existence of the directory
    int cd_success = chdir(buff);
    if (cd_success < 0){
        strcpy(buff,"No corresponding directory found");
    }else{
        getcwd(buff,length);
    }
    return buff;
}

void send_msg(const char* msg, int con_sock){
    // send reply code
    if(send(con_sock, msg, BYTE_LENGTH,0) < 0){
        perror("send fail\n");
    }
    else {
        printf("MSG send...%s\n",msg);
    }
}

int command_set(int con_sock, char* command){
    printf("receive set\n");

    char mode_string[N] = { 0 };
    strcpy(mode_string,command+4);
    printf("mode_string = %s\n",mode_string);

    // transfer mode_string to 0/1
    int temp_mode = transfer_mode_to_int(mode_string);
    return set_trans_mode(temp_mode);
}

int command_quit(int con_sock, int data_sock){
    printf("quit: close socketFd\n");
    char buff[BYTE_LENGTH] = { 0 };
    strcpy(buff, "Connect close...\n");

    // if fail to send buff
    if (send(con_sock, buff, BYTE_LENGTH, 0) < 0){
        printf("Error sending msg \n"); 
        return command_quit(con_sock, data_sock);
    }

    // close connection
    close(con_sock); 
    close(data_sock);

    // exit loop
    return FALSE; 
}

int process_msg(int con_sock, int data_sock){
    char buff[BYTE_LENGTH] = {0};
    // receive command
    int bytes = recv(con_sock, buff, BYTE_LENGTH, 0); 
    // if fail to receive from control port: quit the command
    if (bytes < 0){
        printf("Receive error..."); 
        return command_quit(con_sock, data_sock);
    }

    // analyse the command
    enum COMMAND_TYPE command_type = check_command(buff);
    switch(command_type){
    case C_LS :{
        return command_ls(data_sock, con_sock);
    }
    case C_PUT:{
        return command_put(data_sock, con_sock, buff);
    }
    case C_GET:{
        return command_get(data_sock, con_sock, buff);
    }
    case C_CD:{
        return command_cd(data_sock, con_sock, buff);
    }
    case C_PWD:{
        return command_pwd(data_sock, con_sock);
    }
    case C_DELETE:{
        return command_delete(con_sock, buff);
    }
    case C_MKDIR:{
        return command_mkdir(data_sock, con_sock, buff);
    }
    case C_QUIT:{
        return command_quit(con_sock, data_sock);
    }
    case C_SET:{
        int set_success = command_set(con_sock,buff);
        if(set_success<0){
            printf("set fail\n");
            send_msg("502 Command not implemented. Can't set this mode.",con_sock);
        }else{
            printf("set succeed,now trans_mode is %d\n",get_trans_mode());
            send_msg("200 Command okay.",con_sock);
        }
        return TRUE;
    }
    default:{
        send_msg("504 Command not implemented for that parameter.",con_sock);
        return command_quit(con_sock,data_sock);
    }
    }
    return TRUE;
}

int main(){
    printf("Server startup...\n");

    //create control port
    int con_sockfd = creat_socket(PORT_CONTROL_MESSAGE);
    if (con_sockfd < 0){
        printf("con_sockfd create fail\n");
        return 0;
    }

    //create data port
    int data_sockfd = creat_socket(PORT_DATA); 
    if (data_sockfd < 0){
        printf("data_sockfd create fail\n");
        return 0;
    }

    char init_pwd[BYTE_LENGTH] = {0};
    getcwd(init_pwd,BYTE_LENGTH);

    while (1){ 
         // keep server alive
        // set default trans_mode = ascll
        set_trans_mode(TRANSMODE_ASCII);
        reset_init_pwd(init_pwd);

        // create local socket
        // listen and connect control port
        int con_sock = listen_and_connect(con_sockfd);
        int data_sock = listen_and_connect(data_sockfd);

        // process data
        while (process_msg(con_sock, data_sock)){   
        }
    }

    printf("Server shutdown...\n");
    return 0;
}